import os
import enum
import bpy
from functools import cache
from os.path import join, dirname
from bpy.props import BoolProperty
from bpy.utils import previews
from ..log import log

previews_icons = previews.new()  # 用于存所有的缩略图


class Icon:
    icons = {}

    def load_icon(self, icon_path: str) -> 'bpy.types.ImagePreview':
        """加载图标
        输入一个图标路径将图标加载至self.icons内
        用于显示图片
        """
        if icon_path and (icon_path not in previews_icons):
            icon = previews_icons.load(
                icon_path,
                icon_path,
                'IMAGE',
            )
            self.icons[icon_path] = icon
            return icon

    @staticmethod
    def clear_icon() -> None:
        """清理图标
        将所有图标删除"""
        global previews_icons
        if previews_icons:
            bpy.utils.previews.remove(previews_icons)
            Icon.icons.clear()
            previews_icons = None

    @staticmethod
    def new_icon() -> 'bpy.utils.previews.ImagePreviewCollection':
        """新建图标集合,用于存放预览图标的PreviewsCollection"""
        global previews_icons

        if not previews_icons:
            previews_icons = bpy.utils.previews.new()

        return previews_icons

    @staticmethod
    def _get_none_icon() -> 'bpy.types.ImagePreview':
        """反回默认NONE图标"""
        global previews_icons
        if 'NONE' not in previews_icons:
            none_path = join(dirname(dirname(dirname(__file__))),
                             'rsc/icons/asset_none.png')
            log.debug(none_path)
            previews_icons.load('NONE', none_path, 'IMAGE')
        return previews_icons.get('NONE')

    @staticmethod
    def preload_icons() -> None:
        """预加载图标
        在启动blender或是启用插件时加载图标
        """
        Icon._get_none_icon()
        for item in Preferences.all_asset_items_():
            item.load_icon(item.icon_path)


class Data(Icon):
    class AssetType(enum.Enum):
        """资产的类型
        MATERIAL = 'MATERIAL'
        LIGHT = 'LIGHT'
        LUTS = 'LUTS'
        HDR = 'HDR'
        ANIMATION = 'ANIMATION'
        """
        SCENE = 'SCENE'
        OBJECT = 'OBJECT'

        RENDER = 'RENDER'  # 渲染模块,不用作于资产显示
        UNKNOWN = 514

    is_updating: BoolProperty(
        name='是在更新中',
        default=False,
        description='常为False,仅在更新时为True,用于避免循环更新,不常用属性')

    thumbnail_suffix = ['.png', '.jpg', '.jpeg']  # 缩略图后缀列表
    _prefix = 'asset_'  # 资产属性前缀
    asset_structure = {  # 资产分类
        'OBJECT': {
            'category': ['Object', '物体', '产品'],
            'suffix': ['.blend', ],  # TODO 支持 '.obj', '.fbx',
        },
        'SCENE': {
            'category': ['Scene', '场景'],
            'suffix': ['.blend'],
        },
    }
    show_asset_type_item = asset_structure.keys()  # 显示类型项,用于设置显示分类


class Preferences(Data):
    """插件属性设置
    以及一些共用的方法及属性
    """
    from .. import preferences

    @property
    def enabled_render_queue(self) -> bool:
        """启用了"""
        return 'render_queue' in bpy.context.preferences.addons

    # Func
    @classmethod
    def __key_prefix_infix(cls, name: str) -> str:
        """反回前缀和中缀
        输入一个中缀
        获得一个前缀+中缀
        前缀不变
        """
        return f'{cls._prefix}{name.lower()}'

    @classmethod
    def tree_asset_key_(cls, name: str) -> str:
        """反回树的键"""
        return f'{cls.__key_prefix_infix(name)}_tree'

    @classmethod
    def index_key_(cls, name: str) -> str:
        """反回索引的键"""
        return f'{cls.__key_prefix_infix(name)}_index'

    @classmethod
    def asset_items_key_(cls, name: str) -> str:
        """反回资产的键"""
        return f'{cls.__key_prefix_infix(name)}_items'

    @classmethod
    def find_assets_(cls, pref: 'Preferences', show_type: str = None):
        """查找资产的集合
        如果不输入类型则直接找当前显示类型"""
        show_key = show_type.lower() if show_type else pref.show_type_.lower()
        key = cls.asset_items_key_(show_key)
        assets = getattr(pref.asset_items_pointer_, key, None)
        return assets

    def find_trees_(self, name: str) -> 'preferences.asset_tree.FolderTree':
        """查找树集合"""
        tree_key = self.tree_asset_key_(name)
        return getattr(self.asset_tree_pointer_, tree_key, None)

    @classmethod
    def find_active_asset_(cls, pref: 'Preferences', show_type: str):
        """查找活动资产项"""

        assets = Preferences.find_assets_(pref, show_type.lower())
        if not pref.asset_items_pointer_:
            return None
        index_key = cls.index_key_(show_type.lower())
        index = getattr(pref.asset_items_pointer_, index_key, None)
        if assets and isinstance(index, int, ):  # 两个都找到了才反回
            return assets[index]

    @staticmethod
    def get_index_collection_(item, items) -> int:
        """反回项在数据集里面的索引"""
        return items[:].index(item)

    @classmethod
    def all_asset_items_(cls) -> 'list[preferences.asset_items.AssetItem]':
        """
        反回所有的资产项
        包括每一个资产分类的所有资产
        在刷新图标时使用
        """
        asset_items = []
        pref = Preferences.pref_()
        assets = pref.asset_pointer.asset_items

        for key in Preferences.show_asset_type_item:
            asset_key = cls.asset_items_key_(key.lower())
            items = getattr(assets, asset_key)[:]
            asset_items.extend(items)
        return asset_items

    @staticmethod
    def _static_pref() -> 'preferences.AddonProperty':
        """反回插件属性并缓存
        每一次开关插件都需要清除缓存
        静态方法
        反回插件路径的文本,用于设置属性更改名称
        """
        from . import AddonProperty
        return bpy.context.preferences.addons[AddonProperty.bl_idname].preferences

    @classmethod
    def clear_all_asset(cls) -> None:
        """清除所有资产项
        用于在刷新时重新加载
        """

        pref = Preferences.pref_()
        tree = pref.asset_pointer.asset_tree
        items = pref.asset_pointer.asset_items

        def clear_item(category):
            tree_key = cls.tree_asset_key_(category)
            items_key = cls.asset_items_key_(category)
            getattr(tree, tree_key).clear()
            getattr(items, items_key).clear()

        for key in Preferences.show_asset_type_item:
            clear_item(key.lower())

    @staticmethod
    def is_thumbnail(file: str) -> bool:
        """反回文件路径是否是缩略图的布尔值"""
        file_name, suffix = os.path.splitext(file)
        return suffix in Preferences.thumbnail_suffix

    @staticmethod
    def is_asset(file: str, category: str) -> bool:
        """反回输入路径是否是资产的布尔值"""
        file_name, suffix = os.path.splitext(file)
        suf = Preferences.asset_structure[category.upper()]['suffix']
        return suffix in suf

    @staticmethod
    def _selected_assets(items: list['preferences.asset_items.AssetItem']) -> list['preferences.asset_items.AssetItem']:
        """输入一个资产项
        反回选中了的资产
        用于获取可迭代项的选中项
        """
        return [item for item in items if item.selected]

    def get_selected_assets(self, name) -> 'list[preferences.asset_items.AssetItem]':
        """输入一个资产名称反回一个资产的列表,被选中的"""
        pref = self.pref
        return Preferences._selected_assets(pref.find_assets_(pref, name))

    @cache
    def pref_(self=None) -> 'preferences.AddonProperty':
        """高频调用项
        缓存偏好设置
        用于多次调用时不会重复获取
        """
        return Preferences._static_pref()

    @property
    def pref(self) -> 'preferences.AddonProperty':
        """反回插件属性"""
        return Preferences.pref_()

    @property
    def show_type_(self) -> str:
        """反回小写的显示资产类型字符串"""
        show_type = self.pref.show_type
        if not show_type:
            return 'ERROR show_type not'
        return show_type.lower()

    @property
    def asset_tree_pointer_(self) -> 'preferences.asset_tree.TreePointer':
        """反回树
        """
        asset_tree = self.pref.asset_pointer.asset_tree
        return asset_tree

    @property
    def asset_items_pointer_(self) -> 'preferences.asset_items.AssetItemsPointer':
        """反回资产分类项"""
        asset_items = self.pref.asset_pointer.asset_items
        return asset_items

    @property
    def active_trees(self):
        """
        反回活动资产树
        或者说是文件夹
        """
        return self.find_trees_(self.show_type_)

    @property
    def scene_trees(self):
        return self.find_trees_('scene')

    @property
    def object_trees(self):
        return self.find_trees_('object')

    @property
    def active_tree(self):
        """
        反回活动文件夹项索引值
        只有文件夹有索引值
        资产没有
        """
        key = self.index_key_(self.show_type_)
        if not self.asset_tree_pointer_:
            return None

        index = getattr(self.asset_tree_pointer_, key)
        return self.active_trees[index]

    @property
    def active_assets(self, show_type=None) -> 'list[preferences.asset_items.AssetItem]':
        """
        TODO 反回活动资产项
        """
        return Preferences.find_assets_(self.pref, show_type)

    @property
    def active_asset(self) -> 'preferences.asset_items.AssetItem':
        """反回活动资产项
        """
        return self.find_active_asset_(self.pref, self.show_type_)

    @property
    def active_object(self) -> 'preferences.asset_items.AssetItem':
        """反回活动物体"""
        return self.find_active_asset_(self.pref, 'object')

    @property
    def active_objects(self) -> 'list[preferences.asset_items.AssetItem]':
        return self.find_assets_(self.pref, 'object')

    # selected collect
    @property
    def active_selected_collect(self) -> 'preferences.asset_items.SelectCollectItem':
        """反回活动选择集
        如果没有则反回None
        """
        point = self.pref.asset_items_pointer_

        col = point.asset_select_collect
        c_len = len(col)

        if col and c_len:
            index = point.asset_select_collect_index
            if (c_len - 1 < index) or (0 > index):
                index = -1
            return list(point.asset_select_collect)[index]

    @classmethod
    def selected_collect_name_(cls, name) -> str:
        """反回选择集名称"""
        return 'selected_collect_name' + name

    @property
    def active_selected_collect_name(self) -> str:
        """反回活动选择集名称"""
        a = self.active_selected_collect
        if a:
            return self.selected_collect_name_(a.collect_name)
        return ''

    @property
    def selected_collect_items(self) -> 'list[preferences.asset_items.SelectCollectItem]':
        """反回选择集"""
        return getattr(self.pref.asset_items_pointer_, 'asset_select_collect', None)

    # scene

    @property
    def active_scene(self) -> 'preferences.asset_items.AssetItem':
        """反回活动场景
        """
        return self.find_active_asset_(self.pref, 'scene')

    @property
    def active_scenes(self) -> 'list[preferences.asset_items.AssetItem]':
        return self.find_assets_(self.pref, 'scene')

    # mode

    @property
    def selected_objects(self) -> list['preferences.asset_items.AssetItem']:
        """反回已选择了的物体"""
        return self.get_selected_assets('object')

    @property
    def selected_objects_path(self) -> 'list[str]':
        """反回已选择的路径"""
        return [obj.asset_path for obj in self.selected_objects]

    @property
    def selected_scenes(self) -> list['preferences.asset_items.AssetItem']:
        """反回已选择了的场景"""
        return self.get_selected_assets('scene')

    @property
    def selected_scenes_path(self) -> 'list[str]':
        """反回路径"""
        return [obj.asset_path for obj in self.selected_scenes]

    # BOOL
    @property
    def is_asset_mode(self) -> bool:
        """如果是资产模式,侧使用导入功能"""
        return self.pref.mode == 'ASSET'

    @property
    def is_pool_mode(self) -> bool:
        """如果是资产池模式则选择场景与产品进行渲染"""
        return self.pref.mode == 'POOL'

    @property
    def is_object_mode(self) -> bool:
        """如果是物体模式,侧使用导入功能"""
        return self.pref.mode == 'OBJECT'

    @property
    def is_scene_mode(self) -> bool:
        """如果是场景模式则选择场景与产品进行渲染"""
        return self.pref.mode == 'SCENE'

    @property
    def is_fixed_mode(self) -> bool:
        """是固定模式
        固定场景或固定物体
        替换另一项
        """
        return self.is_scene_mode or self.is_object_mode

    @property
    def is_show_preferences(self) -> bool:
        """反回是否显示的界面是否为偏好设置的布尔值
        """
        return self.pref.show_preferences_property

    @property
    def is_matching_scene_active(self) -> bool:
        """活动场景是匹配场景"""
        return self.active_scene and (self.active_scene.asset_name == bpy.context.scene.name)

    # show type
    @property
    def is_show_object(self) -> bool:
        """是显示物体"""
        return self.pref.show_type == 'OBJECT'

    @property
    def is_show_scene(self) -> bool:
        """是显示场景"""
        return self.pref.show_type == 'SCENE'

    @property
    def is_show_render_queue(self) -> bool:
        """是渲染队列模式
        """
        return self.pref.show_type == 'RENDER_QUEUE'

    # import
    @property
    def is_preview_import(self) -> bool:
        """预览导入模式"""
        return self.pref.import_mode == 'PREVIEW'

    @property
    def is_direct_import(self) -> bool:
        """直接导入模式"""
        return self.pref.import_mode == 'IMPORT'

    # oth
    @classmethod
    def _obj_is_location(cls, obj: 'bpy.types.Object') -> bool:
        """反回输入物体是否是定位框的布尔值"""
        return obj.name.startswith('TAG-')

    @property
    def location_box(self) -> 'bpy.context.object':
        """反回定位box
        需要对齐的空物体
        单个对齐框时使用
        如果活动物体是定位框的话将会优先反回活动项
        """
        o = bpy.context.object
        if o and self._obj_is_location(o):
            return o

        for obj in bpy.context.scene.objects:
            if self._obj_is_location(obj):
                return obj

    @property
    def location_box_list(self) -> 'list[bpy.types.Object]':
        """反回定位box列表
        多个定位框时使用

        """
        return [obj for obj in bpy.context.scene.objects if obj.name.startswith('TAG-')]

    # UPDATE

    def update_queue_scene(self) -> None:
        """更新队列场景
        用于设置多定位框的情况
        只有在场景模式切换
        或是在场景模式里面切换活动场景资产时才会更新"""
        if not self.active_scene:
            return

        name = self.active_scene.asset_name
        is_ok = self.is_scene_mode and (
            not self.is_matching_scene_active) and self.active_scene

        def import_and_set_scene():
            """导入并设置场景"""
            from ...ops.asset_utils import AssetUtils
            active = self.active_scene
            image = AssetUtils()
            image.asset_path = active.asset_path
            image.asset_type = active.asset_type
            image.import_scene_whole(bpy.context)

        if is_ok:
            log.debug(f'update_scene {self.is_matching_scene_active, self}')
            scene = bpy.data.scenes.get(name)
            if scene:  # 存在此场景,不过不是活动项
                bpy.context.window.scene = scene
            else:
                import_and_set_scene()

    @property
    def estimated_export_time(self):
        """预计导出完成时间
        9.9个gb差不多1分钟导出完成
        反回秒
        """
        return round(self.estimated_export_size * 6, 1)

    @property
    def estimated_export_size(self) -> float:
        """预计导出的所有文件总大小
        gb
        """
        if self.is_scene_mode:
            return -1

        return round(
            sum(sc.asset_size + obj.asset_size for sc in self.selected_scenes for obj in self.selected_objects), 2)

    @property
    def estimated_export_count(self) -> int:
        """预计导出文件数量"""
        if self.is_scene_mode:
            return self.pref.export_assign_file_count

        return len([1 for _ in self.selected_scenes for _ in self.selected_objects])

    @property
    def is_show_select_collect(self) -> bool:
        """是显示选择集合的布尔值"""
        return self.is_scene_mode and self.is_show_object

    @property
    def is_use_selected_collect(self) -> bool:
        """反回是否使用选择集的一个布尔属性
        用于更改布尔属性的值
        """
        point = self.pref.asset_items_pointer_
        index = point.asset_select_collect_index != -1
        items = point.asset_select_collect
        return bool(self.is_show_select_collect and index and items)
